// Grant Foster
// gfoster5

#include "Parser.h"

// maps instructions to their binary equivalent and type
struct {
	char *name;
	char *binMapping;
	char *type;
} instructions[] = {
				{"add",     "100000", "rType"},
				{"sub",     "100010", "rType"},
				{"and",     "100100", "rType"},
				{"or",      "100101", "rType"},
				{"sll",     "000000", "rType"},
				{"nop",     "000000", "rType"},
				{"slt",     "101010", "rType"},
				{"sra",     "000011", "rType"},
				{"srl",     "000010", "rType"},
				{"lw",      "100011", "iType"},
				{"sw",      "101011", "iType"},
				{"andi",    "001100", "iType"},
				{"ori",     "001101", "iType"},
				{"beq",     "000100", "iType"},
				{"bne",     "000101", "iType"},
				{"slti",    "001010", "iType"},
				{"addi",    "001000", "iType"},
				{"addiu",   "001001", "iType"},
				{"blez",    "000110", "iType"},
				{"bltz",    "000001", "iType"},
				{"la",      "PSEUDO", "iType"},
				{"li",      "PSEUDO", "iType"},
				{"blt",     "PSEUDO", "iType"},
				{"ble",     "PSEUDO", "iType"},
				{"j",       "000010", "jType"},
				{"jal",     "000011", "jType"},
				{"syscall", "001100", "diff"},
				{"jr",      "001000", "diff"},
				{NULL,      0,        NULL}
};


// maps register names to their binary equivalent
struct {
	char *name;
	char *binMapping;
} registers[] = {
				{"$zero", "00000"},
				{"$at",   "00001"},
				{"$ra",   "11111"},
				{"$v0",   "00010"},
				{"$v1",   "00011"},
				{"$a0",   "00100"},
				{"$a1",   "00101"},
				{"$a2",   "00110"},
				{"$a3",   "00111"},
				{"$t0",   "01000"},
				{"$t1",   "01001"},
				{"$t2",   "01010"},
				{"$t3",   "01011"},
				{"$t4",   "01100"},
				{"$t5",   "01101"},
				{"$t6",   "01110"},
				{"$t7",   "01111"},
				{"$s0",   "10000"},
				{"$s1",   "10001"},
				{"$s2",   "10010"},
				{"$s3",   "10011"},
				{"$s4",   "10100"},
				{"$s5",   "10101"},
				{"$s6",   "10110"},
				{"$s7",   "10111"},
				{"$t8",   "11000"},
				{"$t9",   "11001"},
				{NULL,   0} 
};


// takes fp and takes all text except for comments and puts it in tempPtr
void removeComments(FILE *fp, FILE *tempPtr)
{
	char ch;
	int i = 0;

	while ((ch = getc(fp)) != EOF)
	{
		if (ch == '#')
		{
			do
			{
				ch = getc(fp);
			} 
			while (ch != '\n');
		}

		fseek(tempPtr, 1, i++);
		putc(ch, tempPtr);
	}

	// close original file; don't need it anymore
	fclose(fp);
}

// add space after "syscall" and "nop" for tokenizing purposes
void prepareCommands(FILE *fp, FILE *tempPtr)
{

	char line[MAXNUMCHARS];

	while (fgets(line, sizeof(line), fp)) 
	{
		int len = strlen(line);

		if ((line[len - 2] == 'l' && 
			 line[len - 3] == 'l' && 
			 line[len - 4] == 'a' && 
			 line[len - 5] == 'c' && 
			 line[len - 6] == 's' && 
			 line[len - 7] == 'y' && 
			 line[len - 8] == 's') ||
			(line[len - 2] == 'p' &&
			 line[len - 3] == 'o' &&
			 line[len - 4] == 'n'))
		{
			char *p = strtok(line, "\n");
			fputs(strcat(strcat(p, " "), "\n"), tempPtr);
		}
		else
		{
			fputs(line, tempPtr);
		}
	}
	
	fclose(fp);
}

// determines whether or not the input is an instruction
bool isInstruction(char *str)
{

	for (int i = 0; instructions[i].name != NULL; i++)
	{
		if (!strcmp(str, instructions[i].name))
		{
			return true;
		}
	}

	return false;

}

// determines the instruction type of the instruction passed in
char* instructionType(char *str)
{

	for (int i = 0; instructions[i].name != NULL; i++)
	{
		if (!strcmp(str, instructions[i].name))
		{
			return instructions[i].type;
		}
	}

	return NULL;

}

// gets the binary mapping of the instruction passed in
char* binInstrMapping(char *str)
{

	for (int i = 0; instructions[i].name != NULL; i++)
	{
		if (!strcmp(str, instructions[i].name))
		{
			return instructions[i].binMapping;
		}
	}

	return NULL;

}

// gets the binary mapping of the register passed in
char* binRegMapping(char *str)
{

	for (int i = 0; registers[i].name != NULL; i++)
	{
		if (!strcmp(str, registers[i].name))
		{
			return registers[i].binMapping;
		}
	}

	return NULL;

}


// first arg is the number string, second is the desired length of binary
// string that is produced. converts from string to int to binary string
char* strToBin(char *str, int length)
{
	int num = atoi(str);
	int shift;
	int paddedBits = length - 1;

	FILE *tempFwrite = fopen("strToBin_TEMP", "w");   

	fputs(" ", tempFwrite);

	for (paddedBits; paddedBits >= 0; paddedBits--)
	{
		shift = num >> paddedBits;

		if (shift & 1)
		{
			fputs("1", tempFwrite);
		}
		else
		{
			fputs("0", tempFwrite);
		}
	}

	fputs(" ", tempFwrite);
	
	fclose(tempFwrite);
	FILE* tempFread = fopen("strToBin_TEMP", "r");

	char line[length + 2];
	fgets(line, sizeof(line), tempFread);
	char *finalStr = strtok(line, " ");

	fclose(tempFread);

	// remove temp file from current directory
	remove("strToBin_TEMP");

	return finalStr;
}

// first arg is the number, second is the desired length of binary
// string that is produced. converts from int to binary string
char* intToBin(int num, int length)
{
	int shift;
	int paddedBits = length - 1;

	FILE *tempFwrite = fopen("strToBin_TEMP", "w");   

	fputs(" ", tempFwrite);

	for (paddedBits; paddedBits >= 0; paddedBits--)
	{
		shift = num >> paddedBits;

		if (shift & 1)
		{
			fputs("1", tempFwrite);
		}
		else
		{
			fputs("0", tempFwrite);
		}
	}

	fputs(" ", tempFwrite);
	
	fclose(tempFwrite);
	FILE* tempFread = fopen("strToBin_TEMP", "r");

	char line[length + 2];
	fgets(line, sizeof(line), tempFread);
	char *finalStr = strtok(line, " ");

	fclose(tempFread);

	// remove temp file from current directory
	remove("strToBin_TEMP");

	return finalStr;
}

// extends an n-bit binary string with the desired passed in padding.
// For example, binExtend("11010", 11) returns the 16 bit binary 
// string "0000000000011010". Useful for the 16 bit immediate value in i-types
char* binExtend(char *str, int padding)
{
	FILE *tempFwrite = fopen("binExtend_TEMP", "w");	
	fputs(" ", tempFwrite);

	for (int i = 0; i < padding; i++)
	{
		fprintf(tempFwrite, "0");
	}
	fprintf(tempFwrite, "%s", str);
	
	fputs(" ", tempFwrite);
	
	fclose(tempFwrite);
	FILE* tempFread = fopen("binExtend_TEMP", "r");

	char line[MAXNUMCHARS];
	fgets(line, sizeof(line), tempFread);
	char *finalStr = strtok(line, " ");

	fclose(tempFread);

	// remove temp file from current directory
	remove("binExtend_TEMP");

	return finalStr;
}

/* 
Calls all the other helper functions in this .c file and does the main
processing. It makes its first pass of the input file to the .data segment
and stores all the relevant data. It then makes a second pass through the 
input file. While it makes its second pass, if the current line in the file 
begins with an instruction, it outputs the appropriate machine code to the 
output file including data from the .data section where appropriate.
*/
void commandParser(FILE *fp, FILE *tempPtr)
{
	char line[MAXNUMCHARS];
	int dataSegSize = 0;

	fseek(fp, 0L, SEEK_SET);

	// get size for data segment struct
	while (fgets(line, sizeof(line), fp))
	{
		if (!strcmp(line, ".data\n"))
		{
			while (fgets(line, sizeof(line), fp))
			{
				if (strcmp(line, "\n"))
				{
					dataSegSize++;
				}
			}
		}
	}
	
	//fprintf(tempPtr, "%i\n", dataSegSize);

	int dataSegMem = 0x00002000;

	// struct for the data segment
	struct {
		char name[MAXNUMCHARS];
		char type[MAXNUMCHARS];
		char value[MAXNUMCHARS];
		int memLocation;
	} dataSeg[dataSegSize + MAXNUMCHARS];

	fseek(fp, 0L, SEEK_SET);
	
	int count = 0;
	char *array[MAXNUMCHARS];
	// get data segment variables etc
	while (fgets(line, sizeof(line), fp))
	{
		if (!strcmp(line, ".data\n"))
		{
			while (fgets(line, sizeof(line), fp))
			{
				int numTokens = 0;
				char *t = strtok(line, ": \t");
				if (strcmp(line, "\n"))
				{
					while (t != NULL)
					{
						array[numTokens] = t;
						t = strtok(NULL, " \t\n");
						numTokens++;
					}
				}
				strcpy(dataSeg[count].name, array[0]);
				strcpy(dataSeg[count].type, array[1]);
				strcpy(dataSeg[count].value, array[2]);
				dataSeg[count].memLocation = dataSegMem;
				if (strstr(dataSeg[count].value, ":") != NULL)
				{
					char *tempArray[MAXNUMCHARS];
					char *tempTok = strtok(dataSeg[count].value, ":");
					int tempNumTokens = 0;
					while (tempTok != NULL)
					{
						tempArray[tempNumTokens] = tempTok;
						tempTok = strtok(NULL, ":");
						tempNumTokens++;
					}
					for (int i = 0; i < atoi(tempArray[1]); i++)
					{
						strcpy(dataSeg[count + 1].name, "cont");
						strcpy(dataSeg[count + 1].type, ".word");
						strcpy(dataSeg[count + 1].value, tempArray[0]);
						dataSeg[count + 1].memLocation = dataSegMem + 4;
						count++;
						dataSegMem += 4;
					}
				}
				else
				{
					count++;
					dataSegMem += 4;
				}
			}
		}
	}

	fseek(fp, 0L, SEEK_SET);

	int instrMem = 0x00000000;
	
	// go through the file line by line and parse commands
	while (fgets(line, sizeof(line), fp))
	{
		
		char *array[MAXNUMCHARS];
		int numTokens = 0;
		char *p = strtok(line, ", \t");

		// tokenize current line
		while (p != NULL)
		{
			array[numTokens] = p;
			numTokens++;
			p = strtok(NULL, ", \n\t");
		}

		if (!strcmp(line, ".data\n"))
		{
			break;
		}

		if (symbolsOn)
		{
			bool isOnlySpaces = false;
			char *temp = strtok(line, ": \t\n");
			if (!line[strspn(line, " \n\t")])
			{
				isOnlySpaces = true;
			}
			if (strcmp(line, "\n") && 
				strcmp(line, ".text") && 
				!isInstruction(array[0]) &&
				!isOnlySpaces)
			{
				fprintf(tempPtr, "0x%08X\t", instrMem);
				fprintf(tempPtr, "%s\n", temp);
			}
		}

		if (listOn)
		{
			bool isOnlySpaces = false;
			char *temp = strtok(line, " \t\n");
			if (!line[strspn(line, " \n\t")])
			{
				isOnlySpaces = true;
			}
			if (strcmp(line, "\n") && 
				strcmp(line, ".text") && 
				!isInstruction(array[0]) &&
				!isOnlySpaces)
			{
				fprintf(tempPtr, "\t\t\t\t\t\t\t%s\n", temp);
			}
		}

		if (isInstruction(array[0]))
		{
			if (!strcmp(instructionType(array[0]), "rType"))
			{
				char *op = "000000";
				char *rs = binRegMapping(array[2]);
				char *rt = binRegMapping(array[3]);
				char *rd = binRegMapping(array[1]);
				char *sa = "00000";
				char *funct = binInstrMapping(array[0]);

				if (!strcmp(array[0], "nop"))
				{
					rs = "00000";
					rt = "00000";
					rd = "00000";
				}

				if (!strcmp(array[0], "sll") || 
					!strcmp(array[0], "sra") || 
					!strcmp(array[0], "srl"))
				{
					rs = "00000";
					rt = binRegMapping(array[2]);
					rd = binRegMapping(array[1]);
					sa = strToBin(array[3], 5);
				}
		
				if (listOn)
				{
					if (!strcmp(array[0], "nop"))
					{
						fprintf(tempPtr, "0x%08X\t", instrMem);
						fprintf(tempPtr, "%s%s%s%s%s%s\t", 
									op, rs, rt, rd, sa, funct);
						fprintf(tempPtr, "nop\n");
					}
					else
					{
						fprintf(tempPtr, "0x%08X\t", instrMem);
						fprintf(tempPtr, "%s%s%s%s%s%s\t", 
									op, rs, rt, rd, sa, funct);
						fprintf(tempPtr, "%s %s, %s, %s\n", 
									array[0], array[1], array[2], array[3]);
					}
				} 
				else if (symbolsOn)
				{
					fprintf(tempPtr, "");
				}
				else
				{
					fprintf(tempPtr, "%s%s%s%s%s%s\n", 
									op, rs, rt, rd, sa, funct);
				}
				instrMem += 4;
			}
			
			// This section also contains all the pseudo instructions
			else if (!strcmp(instructionType(array[0]), "iType"))
			{
				char *op = binInstrMapping(array[0]);
				char *rs = binRegMapping(array[2]);
				char *rt = binRegMapping(array[1]);
				char *offset = "****************";
				char *immArray[2];

				if (!strcmp(array[0], "lw") ||
					!strcmp(array[0], "sw"))
				{
					// tokenize array[2]
					
					int numTokens = 0;
					char *p = strtok(array[2], "(");
					
					while (p != NULL)
					{
						immArray[numTokens] = p;
						numTokens++;
						p = strtok(NULL, ")");
					}

					op = binInstrMapping(array[0]);
					rs = binRegMapping(immArray[1]);
					rt = binRegMapping(array[1]);
					offset = binExtend(strToBin(immArray[0], 5), 11);
				}

				else if (!strcmp(array[0], "blez") ||
					!strcmp(array[0], "bltz"))
				{
					rs = binRegMapping(array[1]);
					rt = "00000";
					offset = "****************";
					//offset = binExtend(strToBin(array[2], 5), 11);
				}

				else if (!strcmp(array[0], "bne") ||
					!strcmp(array[0], "beq"))
				{
					rs = binRegMapping(array[1]);
					rt = binRegMapping(array[2]);
					offset = "****************"; //array[3];
				}

				// PSEUDO INSTRUCTION
				else if (!strcmp(array[0], "la"))
				{
					op = binInstrMapping("addi");
					rs = binRegMapping("$zero");
					//offset = array[2];
					for (int i = 0; dataSeg[i].name != NULL; i++)
					{
						if (!strcmp(array[2], dataSeg[i].name))
						{
							offset = intToBin(dataSeg[i].memLocation, 16); 
							break;
						}
					}
				}
				
				// PSEUDO INSTRUCTION
				else if (!strcmp(array[0], "li"))
				{
					op = binInstrMapping("addiu");
					rs = binRegMapping("$zero");
					offset = strToBin(array[2], 16);
				}

				// PSEUDO INSTRUCTION
				else if (!strcmp(array[0], "blt"))
				{
					if (listOn)
					{
						fprintf(tempPtr, "0x%08X\t", instrMem);
						// translate to slt
						fprintf(tempPtr, "%s%s%s%s%s%s\t", 
											"000000",
											rs,
											rt,
											binRegMapping("$at"),
											"00000",
											binInstrMapping("slt"));
						fprintf(tempPtr, "%s %s, %s, %s\n", 
									array[0], array[1], array[2], array[3]);
					}
					else if (symbolsOn)
					{
						fprintf(tempPtr, "");
					}
					else
					{
						// translate to slt
						fprintf(tempPtr, "%s%s%s%s%s%s\n", 
											"000000",
											rs,
											rt,
											binRegMapping("$at"),
											"00000",
											binInstrMapping("slt"));
					}
					instrMem += 4;

					// translate to bne
					op = binInstrMapping("bne");
					rs = binRegMapping("$at");
					rt = binRegMapping("$zero");
					offset = "****************"; //strToBin(array[3], 16);
				}
				
				// PSEUDO INSTRUCTION
				else if (!strcmp(array[0], "ble"))
				{
					if (listOn)
					{
						fprintf(tempPtr, "0x%08X\t", instrMem);
						// translate to slt
						fprintf(tempPtr, "%s%s%s%s%s%s\t", 
											"000000",
											rs,
											rt,
											binRegMapping("$at"),
											"00000",
											binInstrMapping("slt"));
						fprintf(tempPtr, "%s %s, %s, %s\n", 
									array[0], array[1], array[2], array[3]);
					}
					else if (symbolsOn)
					{
						fprintf(tempPtr, "");
					}
					else
					{
						// translate to slt
						fprintf(tempPtr, "%s%s%s%s%s%s\n", 
											"000000",
											rs,
											rt,
											binRegMapping("$at"),
											"00000",
											binInstrMapping("slt"));
					}
					instrMem += 4;

					// translate to beq
					op = binInstrMapping("beq");
					rs = binRegMapping("$at");
					rt = binRegMapping("$zero");
					offset = "****************"; //strToBin(array[3], 16);

				}
				
				else
				{
					offset = strToBin(array[3], 16);
				}

				if (listOn)
				{
					fprintf(tempPtr, "0x%08X\t", instrMem);
					fprintf(tempPtr, "%s%s%s%s\t", op, rs, rt, offset);
					if (!strcmp(array[0], "lw") ||
						!strcmp(array[0], "sw"))
					{
						fprintf(tempPtr, "%s %s, %s(%s)\n",
										array[0], array[1], 
										immArray[0], immArray[1]); 
					}
					else if (!strcmp(array[0], "la") ||
							!strcmp(array[0], "li") ||
							!strcmp(array[0], "bltz") ||
							!strcmp(array[0], "blez"))
					{
						fprintf(tempPtr, "%s %s, %s\n", 
										array[0], array[1], array[2]);
					}
					else if (strcmp(array[0], "blt") &&
							strcmp(array[0], "ble"))
					{
						fprintf(tempPtr, "%s %s, %s, %s\n", 
									array[0], array[1], array[2], array[3]);
					}
					else
					{
						fprintf(tempPtr, "\n");
					}
				}
				else if (symbolsOn)
				{
					fprintf(tempPtr, "");
				}
				else
				{
					fprintf(tempPtr, "%s%s%s%s\n", op, rs, rt, offset);
				}
				instrMem += 4;
			}

			else if (!strcmp(instructionType(array[0]), "jType"))
			{
				char *op = binInstrMapping(array[0]);
				char *target = "**************************";

				if (listOn)
				{
					fprintf(tempPtr, "0x%08X\t", instrMem);
					fprintf(tempPtr, "%s%s\t", op, target);
					fprintf(tempPtr, "%s %s\n", array[0], array[1]);
				}
				else if (symbolsOn)
				{
					fprintf(tempPtr, "");
				}
				else
				{
					fprintf(tempPtr, "%s%s\n", op, target);
				}

				instrMem += 4;
			}

			else if (!strcmp(instructionType(array[0]), "diff"))
			{
				if (!strcmp(array[0], "syscall"))
				{
					if (listOn)
					{
						fprintf(tempPtr, "0x%08X\t", instrMem);
						fprintf(tempPtr, "%s\t", strToBin("12", 32));
						fprintf(tempPtr, "syscall\n");
					}
					else if (symbolsOn)
					{
						fprintf(tempPtr, "");
					}
					else
					{
						fprintf(tempPtr, "%s\n", strToBin("12", 32));
					}
					instrMem += 4;
				}

				if (!strcmp(array[0], "jr"))
				{	
					char *sp = "000000";
					char *rs = binRegMapping(array[1]);
					char *ten = "0000000000";
					char *hint = "00000"; // lets see if that works!
					char *jr = binInstrMapping(array[0]);
					
					if (listOn)
					{
						fprintf(tempPtr, "0x%08X\t", instrMem);
						fprintf(tempPtr, "%s%s%s%s%s\t",
											sp, rs, ten, hint, jr);
						fprintf(tempPtr, "%s %s\n", array[0], array[1]);
					}
					else if (symbolsOn)
					{
						fprintf(tempPtr, "");
					}
					else
					{
						fprintf(tempPtr, "%s%s%s%s%s\n", 
											sp, rs, ten, hint, jr);
					}
					instrMem += 4;
				}
			}
		} 	
	}

	if (!symbolsOn)
	{
		fprintf(tempPtr, "\n");
	}

	int curr = 0;
	// begin .data segment printing here
	while (curr < count)
	{
		if (listOn)
		{
			if (!strcmp(dataSeg[curr].type, ".asciiz"))
			{
				fprintf(tempPtr, "%s:\n", dataSeg[curr].name);
				fprintf(tempPtr, "0x%08X\t", dataSeg[curr].memLocation);
				fprintf(tempPtr, "********************************\n");
			}
			else
			{
				if (strcmp(dataSeg[curr].name, "cont"))
				{
					fprintf(tempPtr, "%s:\n", dataSeg[curr].name);
				}
				else
				{
					fprintf(tempPtr, "");
				}
				fprintf(tempPtr, "0x%08X\t", dataSeg[curr].memLocation);
				fprintf(tempPtr, "%s\n", strToBin(dataSeg[curr].value, 32));
			}
		}
		else if (symbolsOn)
		{
			curr = count;
			for (int i = 0; i < count; i++)
			{
				if (strcmp(dataSeg[i].name, "cont"))
				{
					fprintf(tempPtr, "0x%08X\t", dataSeg[i].memLocation);
					fprintf(tempPtr, "%s\n", dataSeg[i].name);
				}
			}
		}
		else
		{
			if (!strcmp(dataSeg[curr].type, ".asciiz"))
			{
				fprintf(tempPtr, "********************************\n");
			}
			else
			{
				fprintf(tempPtr, "%s\n", strToBin(dataSeg[curr].value, 32));
			}
		}
	
		curr++;
	}

	fclose(fp);
}
